package com.zjl.redis.第16章_分布式锁;

import cn.hutool.core.lang.UUID;
import io.lettuce.core.api.sync.RedisCommands;
import org.redisson.Redisson;
import org.redisson.RedissonRedLock;
import org.redisson.api.*;
import org.redisson.config.Config;
import org.redisson.connection.ConnectionManager;
import org.redisson.connection.SentinelConnectionManager;

import java.math.BigDecimal;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * ******简单来说，用多台无关的 redis 五台分别加锁，当大多数认为加锁成功，才认为获取锁******
 *
 * Redis也提供了Redlock算法，用来实现基于多个实例的分布式锁。
 * 锁变量由多个实例维护，即使有实例发生了故障，锁变量仍然是存在的，客户端还可以完成锁操作。
 * Redlock算法是实现高可靠分布式锁的一 种有效解决方案，可以在实际开发中使用。最下方还有笔记
 *
 *
 * 红锁算法
 * 在算法的分布式版本中,我们假设我们有N个Redis主节点。这些节点是完全独立的，所以我们不使用复制或任何其他隐式协调系统。
 * 我们已经描述了如何在单个实例中安全地获取和释放锁。我们想当然地识为算法会使用这个方法在单个实例中获取和释放锁。
 * 在我们的示例中，我们设置N=5,这是一个合理的值，
 * 因此我们需要在不同的计算机或虚拟机上运行5个Redis master,以确保它们以几乎独立的方式发生故障。
 *
 * 为了获取锁，客户端执行以下操作:
 *      1.它以毫秒为单位获取当前时间。
 *      2.依次尝试从N个实例，使用相同的key和随机值(例如UUID)获取锁。当向Redis请求获取锁时，
 *        客户端应该设置一个超时时间，这个超时时间应该小于锁的失效时间。
 *        例如你的锁自动失效时间为10秒，则超时时间应该在5-50毫秒之间。
 *        这样可以防止客户端在试图与一个宕机的Redis节点对话时长时间处于阻塞状态。
 *        如果一个实例不可用，客户端应该尽快尝试去另外一个Redis实例请求获取锁;
 *      3.客户端通过从当前时间减去步骤1中获得的时间戳来计算获得锁所用的时间。
 *        当且仅当客户端能够在大多数实例(至少N/2+1个)中获得锁时，并且获得锁的总时间小于锁有效期，则认为获得了锁。
 *      4.如果获得了锁，则其有效时间被认为是初始有效时间减去经过的时间，如步骤3中计算的那样。
 *      5.如果客户端由于某种原因未能获得锁(要么无法锁定N/2+1个实例，要么有效期为负)，
 *        它将尝试解锁所有实例(即使是它认为不能锁定的实例)可以锁定)。
 *
 * 该方案为了解决数据不一致的问题，直接舍弃了异步复制只使用master节点，
 * 同时由于舍弃了slave, 为了保证可用性，引入了N个节点，官方建议是5。
 * 客户端只有在满足下面的这两个条件时，才能认为是加锁成功。
 *      条件1:客户端从超过半数(大于等于N/2+1)的Redis实例上成功获取到了锁;
 *      条件2:客户端获取锁的总耗时没有超过锁的有效时间。
 *
 * @see org.redisson.RedissonLock
 * 的 tryAcquireAsync 方法 中的引用的
 *   tryLockInnerAsync  方法
 *
 *   发现底层用的也是 lua 脚本
 *
 */
public class C2_Redisson实现分布式锁 {

    private static String AMT = "amt";
    private static Integer NUM = 100;

    public static void main(String[] args) throws InterruptedException {

        RedisCommands<String, String> redis = B4_自定义工厂锁.getRedisCommands();//获取连接redis的类

        redis.set(AMT, String.valueOf(NUM));
        AtomicInteger atomicInteger = new AtomicInteger(0);//目的是 线程啥时候停止,没有其他目的
        ExecutorService threadPool = new ThreadPoolExecutor(10, 10, 2L, TimeUnit.SECONDS, new LinkedBlockingDeque<>(10000));

        RLock rLock = 各种模式锁的获取();

        for (int i = 0; i < NUM; i++) {
            threadPool.execute(() -> {
                rLock.lock(10, TimeUnit.SECONDS);//只获取10 秒钟的锁，获取不到 抛错
                try {


                    //添加  业务代码
                    //Redisson把你的所有分布式锁 的绝大部分的问题都解决了  除了集群CAP
                    //和Lock   用法一样
                    atomicInteger.incrementAndGet();
                    BigDecimal bigDecimal = new BigDecimal(redis.get(AMT));
                    BigDecimal subtract = bigDecimal.subtract(BigDecimal.valueOf(1));


                    redis.set(AMT, subtract.toString());
                } finally {
                    /**
                     * 如果直接rLock.unlock();解锁
                     * 在超高并发的时候   会出现
                     * attempt to unlock lock， not locked by current thread by node id :
                     *    尝试按节点id解锁当前线程未锁定的锁
                     *
                     *    Redisson  会自动续命  续锁的超时时间
                     *    续命 时间为  超时时间的  三分之一
                     */
                    //模拟  分布式下的数据更新的   分布式安全
                    if (rLock.isLocked()) {//如过他还在锁定状态
                        if (rLock.isHeldByCurrentThread()) {//是这个线程持有的锁
                            rLock.unlock();//解锁
                        }
                    }
                }

            });
        }

        while (atomicInteger.get() < NUM) {
            System.out.println("执行次数:" + atomicInteger.get());
            System.out.println("剩余金额:" + redis.get(AMT));
            TimeUnit.SECONDS.sleep(1);
        }
        threadPool.shutdown();
        System.out.println("---------------------");
        System.out.println(redis.get(AMT));


    }

    private static RLock 各种模式锁的获取() throws InterruptedException {
        Redisson redisson1 = redisson1();
        Redisson redisson2 = redisson2();
        Redisson redisson3 = redisson3();
        RLock rLock;
        String locKey = "lock" + UUID.randomUUID().toString();//锁的  类别--什么锁
        if (true) {
            rLock = redisson1.getFairLock(locKey);//一台锁
        } else if (false) {
            rLock = redisson1.getLock(locKey);//一台锁
        } else if (false) {//获取 红锁

            RLock rLock1 = redisson1.getLock("lock1");
            RLock rLock2 = redisson2.getLock("lock2");
            RLock rLock3 = redisson3.getLock("lock3");

            rLock = redisson1.getRedLock(rLock1, rLock2, rLock3);//获取 红锁
            rLock = new RedissonRedLock(rLock1, rLock2, rLock3);//获取 红锁  已不推荐
            rLock = redisson1.getMultiLock(rLock1, rLock2, rLock3);//推荐  多重锁
            rLock.lock(10, TimeUnit.SECONDS);//如果没有手动解开的话，10秒钟后将会自动解开，相当于 redis的过期时间
            rLock.tryLock(100, 10, TimeUnit.SECONDS);//多了获取锁的等待时间 100

        }

        return rLock;
    }

    //获取redis 锁1 这三台应该获取多台不同的redis服务器
    public static Redisson redisson1() {
        return (Redisson) Redisson.create(getConfig());
    }

    //获取redis 锁2
    public static Redisson redisson2() {
        return (Redisson) Redisson.create(getConfig());
    }

    //获取redis 锁3
    public static Redisson redisson3() {
        return (Redisson) Redisson.create(getConfig());
    }

    public static Config getConfig() {//配置
        Config config = new Config();

//        config.useClusterServers()//多台无关 redis 地址
//                .addNodeAddress("redis://82.157.71.243:6379","redis://82.157.71.243:6379")//多机
//                .setPassword("er485g96er8").setClientName("test1");

//        config = Config.fromYAML("yaml文件");
        config.setLockWatchdogTimeout(1000);//设置 锁的有效时间，也就是 redis的key的过期时间  单位毫秒
        config.useSingleServer()
                .setAddress("redis://82.157.71.243:6379")//单机
                .setDatabase(0)//使用的redis 的哪个库【0-15】  默认0
                .setPassword("er485g96er8")//密码
                .setConnectionPoolSize(32)//连接池大小
                .setRetryAttempts(3)//命令失败重试次数）
                .setRetryInterval(1000)//命令重试发送时间间隔，单位：毫秒）
                .setConnectionMinimumIdleSize(20)//从节点最小空闲连接数）


        ;

        return config;
    }//分布式锁

    private void 各种类() {
        //获取redis
        RedissonClient redisson = Redisson.create(getConfig());//底层是  new Redisson(config);

        RedissonReactiveClient reactiveReactive = redisson.reactive();

        RedissonRxClient redissonRx = redisson.rxJava();


        //实现了 ConcurrentMap<K, V>
        RMap<Object, Object> map = redisson.getMap("lockName");

        RMapReactive<Object, Object> mapReactive = reactiveReactive.getMap("lockName");

        RMapRx<Object, Object> mapRx = redissonRx.getMap("lockName");


        //正常的锁
        RLock lock = redisson.getLock("lockName");

        RLockReactive lockReactive = reactiveReactive.getLock("lockName");

        RLockRx lockRx = redissonRx.getLock("lockName");

    }
}
